<?php

namespace Yxy\YJwt;

use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Support\Env;
use Illuminate\Support\Facades\Cache;
use ReflectionFunction;

/**
 * jwt方法
 */
class JwtAuth
{
    /**
     * token
     * @var string
     */
    protected static string $token;

    /**
     * 获取appKey
     * @return string
     */
    protected static function getAppKey(): string
    {
        $appKey = Env::get('app.app_key');
        if (empty($appKey) || !is_string($appKey)) {
            $appKey = "jwt-auth-app-key";
        }
        return $appKey;
    }

    /**
     * 获取token
     * @param $id
     * @param string $type
     * @param array $params
     * @return array
     */
    protected static function getToken($id, string $type, array $params = []): array
    {
        $host = request()->host();
        $time = time();
        $exp_time = strtotime('+ 30day');
        $params += [
            'iss' => $host,
            'aud' => $host,
            'iat' => $time,
            'nbf' => $time,
            'exp' => $exp_time,
        ];
        $params['jti'] = compact('id', 'type');
        $token = JWT::encode($params, self::getAppKey(), 'HS256');

        return compact('token', 'params');
    }

    /**
     * 验证token
     */
    protected static function verifyToken(): void
    {
        JWT::$leeway = 60;

        JWT::decode(self::$token, new Key(self::getAppKey(), 'HS256'));

        self::$token = null;
    }

    /**
     * 验证
     * @return void
     * @throws Exception
     */
    protected static function verify(): void
    {
        $token = self::$token;
        $md5Token = empty($token) ? '' : md5($token);
        if ($token === 'undefined') {
            throw new Exception(config("yjwt.error.token_undefined"));
        }
        if (!$token || !$tokenData = Cache::get($md5Token)) {
            throw new Exception(config("yjwt.error.token_null"));
        }
        if (!is_array($tokenData) || empty($tokenData) || !isset($tokenData['id'])) {
            throw new Exception(config("yjwt.error.token_data_err"));
        }
    }

    /**
     * 创建token
     * @param $id
     * @param string $type
     * @param array $params
     * @return array
     * @throws Exception
     */
    public static function createToken($id, string $type, array $params = []): array
    {
        $tokenInfo = self::getToken($id, $type, $params);
        $exp = $tokenInfo['params']['exp'] - $tokenInfo['params']['iat'] + 60;

        $res = Cache::set(md5(trim($tokenInfo['token'])), ['id' => $id, 'type' => $type, 'token' => $tokenInfo['token'], 'exp' => $exp], (int)$exp);
        if (!$res) {
            throw new Exception(config("yjwt.error.token_create_fail"));
        }
        return $tokenInfo;
    }

    /**
     * 解析token
     * @param string $token
     * @param callable|null $callback
     * @throws Exception
     */
    public static function parseToken(string $token, callable $callback = null): void
    {
        self::$token = $token;
        self::verify();
        list($headb64, $bodyb64, $cryptob64) = explode('.', self::$token);
        $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64));
        [$id, $type, $pwd] = [$payload->jti->id, $payload->jti->type, $payload->pwd ?? ''];
        try {
            self::verifyToken();
        } catch (\Throwable $e) {
            throw new Exception(config("yjwt.error.token_expire"));
        }
        if (is_callable($callback)) {
            $reflection = new ReflectionFunction($callback);
            $params = $reflection->getParameters();
            $args = [];
            foreach ($params as $param) {
                $name = $param->getName();
                if ($name === 'id') {
                    $args[] = $id;
                } elseif ($name === 'type') {
                    $args[] = $type;
                } elseif ($name === 'pwd') {
                    $args[] = $pwd;
                }
            }
            $callback(...$args);
        }
    }
}